/*
 * Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.cloudphone.apiimpl;

import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
import static com.huawei.cloudphone.api.CloudPhoneParas.DEV_TYPE_CAMERA;
import static com.huawei.cloudphone.api.CloudPhoneParas.DisplayMode.DISPLAY_MODE_FILL;
import static com.huawei.cloudphone.common.CasState.CAS_CONNECT_EXCEPTION;
import static com.huawei.cloudphone.utils.CasMediaUtils.isValidMediaConfig;

import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.Toast;

import com.huawei.cloudphone.BuildConfig;
import com.huawei.cloudphone.api.CloudAppDataListener;
import com.huawei.cloudphone.api.CloudPhoneClipboardListener;
import com.huawei.cloudphone.api.CloudPhoneOrientationChangeListener;
import com.huawei.cloudphone.api.CloudPhoneParas;
import com.huawei.cloudphone.api.CloudPhoneParas.DisplayMode;
import com.huawei.cloudphone.api.CloudPhonePermissionRequestListener;
import com.huawei.cloudphone.api.CloudPhoneStateListener;
import com.huawei.cloudphone.api.CloudPhoneVirtualDevDataListener;
import com.huawei.cloudphone.api.ICloudPhone;
import com.huawei.cloudphone.common.CASLog;
import com.huawei.cloudphone.common.CasConnectorInfo;
import com.huawei.cloudphone.common.CasParcelableMap;
import com.huawei.cloudphone.common.CasState;
import com.huawei.cloudphone.jniwrapper.JNIWrapper;
import com.huawei.cloudphone.service.CasProcessor;
import com.huawei.cloudphone.virtualdevice.VirtualDeviceSession;
import com.huawei.cloudphone.virtualdevice.camera.VirtualCamera;
import com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager;
import com.huawei.cloudphone.virtualdevice.common.RingBufferVirtualDeviceIO;
import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager;
import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol;
import com.huawei.cloudphone.virtualdevice.sensor.VirtualSensorManager;

import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import static com.huawei.cloudphone.api.CloudPhoneParas.DEV_TYPE_SENSOR;

public class CloudPhoneImpl implements ICloudPhone {
    private static final String TAG = "CloudPhoneImpl";
    private static final int STATE_INIT = 0;
    private static final int STATE_DEINIT = 1;
    private static final int STATE_STARTING = 2;
    private static final int STATE_START = 3;
    private static final int STATE_STOP = 4;
    private static final int STATE_PAUSE = 5;
    private static final int STATE_RESUME = 6;
    private static final int CMD_START = 0;
    private static final int CMD_STOP = 1;
    private static final int CMD_PAUSE = 2;
    private static final int CMD_RESUME = 3;
    private static final int CMD_SURFACE_CREATE = 11;
    private static final int CMD_SURFACE_DESTROY = 12;
    private static final int CMD_STATE_CHANGE = 14;
    private static final int CMD_BACKGROUND_TIMEOUT = 15;
    private static final int CMD_RECONNECT = 16;
    private static final int CMD_SET_MEDIA_CONFIG = 17;
    private static final byte CUSTOM_DATA = 18;
    private static final byte IME_DATA =14;
    public static final int RECONNECT_RETRY_MAX_COUNT = 10;
    public static final int SEND_IME_DATA_RETRY_MAX_COUNT = 100;  // 300ms*100，与重连总时长保持一致
    public static final int RECONNECT_INTERVAL_TIME = 2;  // 间隔2s，实际执行为3s

    private final Object mCloudPhoneLock = new Object();
    private CasProcessor mProccessor = null;
    private Map<String, String> mStartParas = null;
    private Context mContext = null;
    private Handler mCmdHandler = null;
    private HandlerThread mCmdHandlerThread = null;
    private Activity mActivity;
    private ViewGroup mViewGroup;
    private SurfaceView mSurfaceView = null;
    private int mDisplayWidth = 0;
    private int mDisplayHeight = 0;
    private int mCurrentState;
    private DisplayMode mDisplayMode = DISPLAY_MODE_FILL;
    private Timer mBackGroundTimer = null;
    private int mAutoReconnectCount = 0;
    private Timer mAutoReconnectTimer = null;
    private Thread mCheckConnectStateThread = null;
    private volatile boolean mIsReconnectTaskRun = false;
    private CloudPhoneStateListener mStateListener = null;
    private CloudPhoneOrientationChangeListener mOrientationChangeListener = null;
    private CloudPhoneClipboardListener mClipboardListener = null;
    private CloudPhonePermissionRequestListener mPermissionListener = null;
    private CloudPhoneVirtualDevDataListener mVirtualDevDataListener = null;
    private CloudAppDataListener mChannelDataListener = null;
    private CASListener mListener = null;
    private volatile boolean mIsStartSuccess = false;
    private CloudPhoneParas.DevType mDevType;
    private CasConnectorInfo mConnectorInfo = null;
    private boolean mIsNewCreated = false;
    private boolean mReconnecting = false;
    private int mBgTimeout = 60 * 1000;
    private int mTouchCount = 0;
    private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
    private Toast toast = null;
    private int surfaceId = -1;   // 避免快速连接时新的窗口已建立，旧的窗口执行销毁操作发送pause

    private long initTime = 0;

    private CloudPhoneImeMgr mImeMgr;

    //virtual devices
    VirtualDeviceSession mVirtualDeviceSession = null;
    RingBufferVirtualDeviceIO mRingBufferVirtualDeviceIO = null;

    public CloudPhoneImpl() {
        mCurrentState = STATE_DEINIT;
    }

    @Override
    public String getVersion() {
        return BuildConfig.VERSION_NAME;
    }

    @Override
    public void init(Context context, CloudPhoneParas.DevType type) throws Exception {
        CASLog.i(TAG, "init called mCurrentState = " + mCurrentState);
        if (mCurrentState != STATE_DEINIT) {
            CASLog.e(TAG, "Not ready for init. current state is " + mCurrentState);
            throw new IllegalStateException("Not ready for init.");
        }

        mProccessor = new CasProcessor();
        mListener = new CASListener();
        mContext = context;

        mProccessor.init();
        mProccessor.registerListener(mListener);

        initVirtualDeviceSession();

        mCmdHandlerThread = new HandlerThread("cmd process");
        if (mCmdHandlerThread == null) {
            CASLog.e(TAG,"Create handler thread failed.");
            throw new RuntimeException("Bind service failed.");
        }
        mCmdHandlerThread.start();
        mCmdHandler = new CmdHandler(mCmdHandlerThread.getLooper());
        mDevType = type;
        mCurrentState = STATE_INIT;
    }

    @Override
    public void deinit() throws Exception {
        if (mCurrentState == STATE_DEINIT) {
            return;
        }
        CASLog.i(TAG, "deinit called");
        mCmdHandlerThread.quit();
        mCmdHandlerThread.join();
        mProccessor.registerListener(null);
        mCmdHandlerThread = null;
        mCurrentState = STATE_DEINIT;
        mProccessor = null;
        mContext = null;
        mStateListener = null;
        mOrientationChangeListener = null;
        mPermissionListener = null;
        mChannelDataListener = null;
        mListener = null;
    }

    @Override
    public void startCloudPhone(Activity activity, ViewGroup view, Map<String, String> params) {
        if (mCurrentState == STATE_DEINIT) {
            CASLog.e(TAG, "Call init first.");
            throw new IllegalStateException("Call init first.");
        }
        CASLog.i(TAG, "startCloudPhone called");
        mActivity = activity;
        mViewGroup = view;
        mStartParas = params;
        mConnectorInfo = new CasConnectorInfo();
        if (!mConnectorInfo.initConnectorParams(mStartParas)) {
            CASLog.e(TAG, "Init connector params failed.");
            throw new IllegalArgumentException("Init connector params failed.");
        }
        mBgTimeout = Integer.parseInt(mConnectorInfo.getBackgroundTimeout()) * 1000;
        mIsStartSuccess = false;
        Message msg = new Message();
        msg.what = CMD_START;
        mCmdHandler.sendMessage(msg);
    }

    @Override
    public void exitCloudPhone() throws Exception {
        CASLog.i(TAG, "exitCloudPhone called");
        synchronized (mCloudPhoneLock) {
            try {
                if ((mCurrentState == STATE_START) || (mCurrentState == STATE_PAUSE) || (mCurrentState == STATE_STARTING)) {
                    CASLog.i(TAG, "exitCloudPhone begin...");
                    mIsReconnectTaskRun = false;
                    if (mCheckConnectStateThread != null) {
                        mCheckConnectStateThread.interrupt();
                        try {
                            mCheckConnectStateThread.join();
                        } catch (InterruptedException e) {
                            CASLog.e(TAG, "exit cloud phone failed. " + e.getMessage());
                        }
                        mCheckConnectStateThread = null;
                    }
                    if (mBackGroundTimer != null) {
                        mBackGroundTimer.cancel();
                        mBackGroundTimer = null;
                    }
                    mProccessor.stopJniRecv();
                    mProccessor.stop(false);
                    mCmdHandler = null;
                    mCurrentState = STATE_INIT;
                    mSurfaceView.setOnTouchListener(null);
                    mSurfaceView.setOnKeyListener(null);
                    mSurfaceView = null;
                    mActivity = null;
                    mViewGroup = null;
                    mAutoReconnectCount = 0;
                    if (mVirtualDeviceSession != null) {
                        mVirtualDeviceSession.stop();
                    }
                    mVirtualDeviceSession = null;
                    mRingBufferVirtualDeviceIO = null;
                    CASLog.i(TAG, "exitCloudPhone end...");
                }
            }  catch (Exception e) {
                CASLog.e(TAG, "exitCloudPhone failed. " + e.getMessage());
            } finally {
                mCloudPhoneLock.notifyAll();
            }
        }
    }

    @Override
    public void setMediaConfig(HashMap<String, String> mediaConfig) {
        if (mCmdHandler == null) {
            return;
        }
        if (!isValidMediaConfig(mediaConfig)) {
            CASLog.e(TAG, "media config is invalid");
            return;
        }
        Message msg = new Message();
        msg.what = CMD_SET_MEDIA_CONFIG;
        msg.obj = mediaConfig;
        mCmdHandler.sendMessage(msg);
    }

    @Override
    public void setDisplayMode(DisplayMode mode) {
        mDisplayMode = mode;
    }

    @Override
    public void sendDataToCloudApp(byte[] data) {
        if (mProccessor != null) {
            mProccessor.sendData(JNIWrapper.CHANNEL, data);
        }
    }

    @Override
    public void registerCloudAppDataListener(CloudAppDataListener listener) {
        mChannelDataListener = listener;
    }

    @Override
    public void registerCloudPhoneStateListener(CloudPhoneStateListener listener) {
        mStateListener = listener;
    }

    @Override
    public void registerOnOrientationChangeListener(CloudPhoneOrientationChangeListener listener) {
        mOrientationChangeListener = listener;
    }

    @Override
    public void registerPermissionRequestListener(CloudPhonePermissionRequestListener listener) {
        mPermissionListener = listener;
    }

    @Override
    public void registerClipboardListener(CloudPhoneClipboardListener listener) {
        mClipboardListener = listener;
    }

    @Override
    public void sendVirtualDeviceData(byte devType, byte[] data) {
        if (mProccessor != null) {
            mProccessor.sendData(devType, data);
        }
    }

    @Override
    public void sendPermissionResult(int requestCode, int grantResults) {
        VirtualDeviceProtocol deviceProtocol = mVirtualDeviceSession.getVirtualDevice();
        if (deviceProtocol == null) {
            return;
        }
        deviceProtocol.processPermissionResult((short) requestCode, grantResults);
    }

    @Override
    public void sendClipboardData(byte[] data) {
        if (mImeMgr != null) {
            mImeMgr.sendClipboardData(data);
        }
    }

    @Override
    public int getRtt() {
        if (mProccessor != null) {
            return mProccessor.getLag();
        } else {
            return 0;
        }
    }

    @Override
    public String getVideoStatisticInfo() {
        if (mProccessor != null) {
            return mProccessor.getVideoStreamStats();
        } else {
            return "";
        }
    }

    @Override
    public String getSimpleStatisticInfo() {
        if (mProccessor != null) {
            return mProccessor.getSimpleStreamStats();
        } else {
            return "";
        }
    }

    @Override
    public String getSensorStatusInfo() {
        VirtualDeviceProtocol virtualDevice = mVirtualDeviceSession.getVirtualDevice();
        if (virtualDevice == null) {
            return "";
        }
        VirtualDeviceManager virtualDeviceManager = virtualDevice.getVirtualDeviceManager(DEV_TYPE_SENSOR);
        if (virtualDeviceManager == null) {
            return "";
        }
        return ((VirtualSensorManager) virtualDeviceManager).getSensorStatus();
    }

    @Override
    public int getState() {
        return mCurrentState;
    }

    public void handleStartCmd(final Activity activity, final ViewGroup views) {
        if (null == activity) {
            CASLog.e(TAG, "handle start cmd fail, activity is null.");
            return;
        }
        if (mCurrentState != STATE_INIT) {
            CASLog.e(TAG, "mCurrentState = " + mCurrentState);
            return;
        }
        CASLog.i(TAG, "handleStartCmd start");
        mCurrentState = STATE_STARTING;
        //创建SurfaceView
        if (mDisplayMode == DISPLAY_MODE_FILL) {
            mSurfaceView = new PhoneView(activity, true);
        } else {
            mSurfaceView = new PhoneView(activity, false);
        }
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            private int curSurfaceId;

            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                SecureRandom random = new SecureRandom();
                curSurfaceId = random.nextInt(10000);
                while (curSurfaceId == surfaceId) {
                    curSurfaceId = random.nextInt(10000);
                }
                surfaceId = curSurfaceId;
                CASLog.i(TAG, "surfaceCreated, surfaceId:" + this.curSurfaceId);
                mIsNewCreated = true;
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                if (this.curSurfaceId != surfaceId) {
                    CASLog.w(TAG, "cancel surfaceChanged(). curSurfaceId:" + this.curSurfaceId + ", surfaceId:" + surfaceId);
                    return;
                }

                mDisplayWidth = Math.min(width, height);
                mDisplayHeight =  Math.max(width, height);
                CASLog.i(TAG, "width = " + mDisplayWidth + " height = " + mDisplayHeight);

                if (!mIsNewCreated) {
                    return;
                }
                mIsNewCreated = false;
                synchronized (mCloudPhoneLock) {
                    try {
                        if (mCmdHandler != null) {
                            Message msg = new Message();
                            msg.obj = holder;
                            msg.what = CMD_SURFACE_CREATE;
                            mCmdHandler.sendMessage(msg);
                        }
                    } catch (Exception e) {
                        CASLog.e(TAG, "Send message failed. " + e.getMessage());
                    } finally {
                        mCloudPhoneLock.notifyAll();
                    }
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if (this.curSurfaceId != surfaceId) {
                    CASLog.w(TAG, "cancel surfaceDestroyed(). curSurfaceId:" + this.curSurfaceId + ", surfaceId:" + surfaceId);
                    return;
                }
                synchronized (mCloudPhoneLock) {
                    try {
                        CASLog.i(TAG, "surfaceDestroyed, surfaceId:" + surfaceId);
                        if (mProccessor != null && mProccessor.getState() != JNIState.JNI_STOPPED) {
                            mProccessor.stop(true);
                        }
                        if (mCmdHandler != null) {
                            Message msg = new Message();
                            msg.what = CMD_SURFACE_DESTROY;
                            mCmdHandler.sendMessage(msg);
                        }
                    } catch (Exception e) {
                        CASLog.e(TAG, "surfaceDestroyed failed. " + e.getMessage());
                    } finally {
                        mCloudPhoneLock.notifyAll();
                    }
                }
            }
        });
        //设置触摸事件监听

        mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean result = true;
                final double ratio = 0.0125;
                int action = event.getActionMasked();
                int id;
                int rawX;
                int rawY;
                int pressure;
                int index;
                long time;
                if (action == MotionEvent.ACTION_DOWN) {
                    initTime = event.getEventTime();
                }
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                    case MotionEvent.ACTION_POINTER_DOWN:
                    case MotionEvent.ACTION_POINTER_UP:
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:
                        if (action == MotionEvent.ACTION_DOWN) {
                            mTouchCount = 1;
                        } else if (action == MotionEvent.ACTION_UP) {
                            mTouchCount ++;
                            CASLog.i(TAG, "mTouchCount = " +mTouchCount);
                        }
                        action = event.getActionMasked();
                        index = event.getActionIndex();
                        id = event.getPointerId(index);
                        pressure = (int) (event.getPressure(event.getActionIndex()) / ratio);
                        rawX = (int) event.getX(index);
                        rawY = (int) event.getY(index);
                        time = event.getEventTime() - initTime;
                        result = sendTouchEvent(id, action, rawX, rawY, pressure, time);
                        break;
                    case MotionEvent.ACTION_MOVE:
                        final int historySize = event.getHistorySize();
                        final int pointerCount = event.getPointerCount();
                        for (int i = 0; i < historySize; i++) {
                            for (int j = 0; j < pointerCount; j++) {
                                id = event.getPointerId(j);
                                rawX = (int) event.getHistoricalX(j, i);
                                rawY = (int) event.getHistoricalY(j, i);
                                pressure = (int) (event.getHistoricalPressure(j, i) / ratio);
                                time = event.getHistoricalEventTime(i) - initTime;
                                result = sendTouchEvent(id, action, rawX, rawY, pressure, time);
                                mTouchCount ++;
                            }
                        }
                        for (int i = 0; i < pointerCount; i++) {
                            id = event.getPointerId(i);
                            rawX = (int) event.getX(i);
                            rawY = (int) event.getY(i);
                            pressure = (int) (event.getPressure(i) / ratio);
                            time = event.getEventTime() - initTime;
                            result = sendTouchEvent(id, action, rawX, rawY, pressure, time);
                            mTouchCount ++;
                        }
                        break;
                    default:
                        CASLog.e(TAG, "unknown action");
                        break;
                }
                return result;
            }
        });

        mSurfaceView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if ((keyCode == KEYCODE_VOLUME_DOWN) || (keyCode == KEYCODE_VOLUME_UP)) {
                    return false;
                }
                if ((keyCode == KEYCODE_BACK) && mDevType == CloudPhoneParas.DevType.DEV_TV) {
                    return false;
                }
                CASLog.i(TAG, "keyCode = " + keyCode + " event = " + event.toString());
                mProccessor.sendKeyEvent(keyCode, event.getAction());
                return true;
            }
        });

        //设置SurfaceView参数
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                views.addView(mSurfaceView);
                mSurfaceView.setVisibility(View.VISIBLE);
                mSurfaceView.setClickable(true);
                mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
                FrameLayout.LayoutParams layoutParams =
                        new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
                mSurfaceView.setLayoutParams(layoutParams);

                mImeMgr = new CloudPhoneImeMgr(mContext, views);
                mImeMgr.setTextWatchListener(new TextWatchListener());
                mImeMgr.setClipboardListener(mClipboardListener);
            }
        });
    }

    private void handleSurfaceCreateCmd(Message msg) {
        synchronized (mCloudPhoneLock) {
            try {
                mProccessor.setCasConnectorInfo(mConnectorInfo);
                CASLog.i(TAG, "handleSurfaceCreateCmd mCurrentState = " + mCurrentState);
                if (mCurrentState == STATE_STARTING) {
                    mProccessor.setSurface(mSurfaceView.getHolder().getSurface());
                    mProccessor.startJniRecv();
                    if (mProccessor.start(false)) {
                        mIsReconnectTaskRun = true;
                        mCheckConnectStateThread = new CheckConnectStateThread();
                        mCheckConnectStateThread.start();
                    }
                    mCurrentState = STATE_START;
                } else if (mCurrentState == STATE_PAUSE) {
                    mProccessor.setSurface(mSurfaceView.getHolder().getSurface());
                    mProccessor.startJniRecv();
                    mProccessor.start(true);
                    mCurrentState = STATE_START;
                    if (mBackGroundTimer != null) {
                        mBackGroundTimer.cancel();
                        mBackGroundTimer = null;
                    }
                } else {
                    CASLog.e(TAG, "mCurrentState = " + mCurrentState);
                }
                CASLog.i(TAG, "handleSurfaceCreateCmd end");
            } catch (Exception e) {
                CASLog.e(TAG, "handleSurfaceCreateCmd failed. " + e.getMessage());
            } finally {
                mCloudPhoneLock.notifyAll();
            }
        }
    }

    private void handleSurfaceDestroyCmd() {
        synchronized (mCloudPhoneLock) {
            try {
                if (mCurrentState == STATE_START) {
                    CASLog.i(TAG, "handleSurfaceDestroyCmd end");
                    mProccessor.pause();
                    mCurrentState = STATE_PAUSE;
                    mBackGroundTimer = new Timer();
                    mBackGroundTimer.schedule(new BackgroundTimerTask(), mBgTimeout);
                    if (mVirtualDeviceSession != null) {
                        mVirtualDeviceSession.stop();
                    }
                }
            } catch (Exception e) {
                CASLog.e(TAG, "handleSurfaceDestroyCmd failed. " + e.getMessage());
            } finally {
                mCloudPhoneLock.notifyAll();
            }
        }
    }

    private void handleSetMediaConfigCmd(Message msg) {
        synchronized (mCloudPhoneLock) {
            try {
                HashMap<String, String> mediaConfig = (HashMap<String, String>) msg.obj;
                CasParcelableMap parcelableMap = new CasParcelableMap();
                parcelableMap.setParcelableMap(mediaConfig);
                mProccessor.setMediaConfig(parcelableMap);
            } catch (Exception e) {
                CASLog.e(TAG, "handleSetMediaConfigCmd failed. " + e.getMessage());
            } finally {
                mCloudPhoneLock.notifyAll();
            }
        }
    }

    private void handleStateChangeCmd(Message msg) {
        synchronized (mCloudPhoneLock) {
            try {
                int state = msg.arg1;
                if (state == CasState.CAS_BACKGROUND_TIMEOUT
                        || state == CasState.CAS_NOTOUCH_TIMEOUT
                        || state == CasState.CAS_TRAIL_PLAY_TIMEOUT
                        || state == CasState.CAS_VERIFY_FAILED
                        || state == CasState.CAS_VERIFY_DECRYPT_FAILED
                        || state == CasState.CAS_VERIFY_AESKEY_INVALID
                        || state == CasState.CAS_VERIFY_AESKEY_QUERY_FAILED
                        || state == CasState.CAS_VERIFY_PARAMETER_INVALID
                        || state == CasState.CAS_VERIFY_PARAMETER_MISSING
                        || state == CasState.CAS_CONNECT_LOST) {
                    if (mCurrentState == STATE_START
                            || mCurrentState == STATE_PAUSE
                            || mCurrentState == STATE_STARTING) {
                        if (mCheckConnectStateThread != null) {
                            mIsReconnectTaskRun = false;
                            mCheckConnectStateThread.interrupt();
                            try {
                                mCheckConnectStateThread.join();
                            } catch (InterruptedException e) {
                                CASLog.e(TAG, "stop CheckConnectStateThread failed. " + e.getMessage());
                            }
                            mCheckConnectStateThread = null;
                        }
                        mProccessor.stopJniRecv();
                        mProccessor.stop(false);
                    }
                } else if (state == CasState.CAS_START_SUCCESS) {
                    mIsStartSuccess = true;
                    if (mVirtualDeviceSession != null) {
                        mVirtualDeviceSession.start();
                    }
                } else if (state == CasState.CAS_SWITCH_FOREGROUND_SUCCESS
                        || state == CasState.CAS_RECONNECT_SUCCESS) {
                    if (mVirtualDeviceSession != null) {
                        mVirtualDeviceSession.start();
                    }
                } else if (state == CasState.CAS_REQUEST_CAMERA_KEY_FRAME) {
                    VirtualDeviceProtocol virtualDevice = mVirtualDeviceSession.getVirtualDevice();
                    if (virtualDevice == null) {
                        return;
                    }
                    VirtualDeviceManager virtualDeviceManager = virtualDevice.getVirtualDeviceManager(DEV_TYPE_CAMERA);
                    if (virtualDeviceManager == null) {
                        return;
                    }
                    ((VirtualCameraManager) virtualDeviceManager).generateKeyFrame();
                }
            } catch (Exception e) {
                CASLog.e(TAG, "handleStateChangeCmd failed. " + e.getMessage());
            } finally {
                mCloudPhoneLock.notifyAll();
            }
        }
    }

    private void handleBackgroundTimeoutCmd() {
        synchronized (mCloudPhoneLock) {
            try {
                if (mCurrentState == STATE_PAUSE) {
                    if (mCheckConnectStateThread != null) {
                        mIsReconnectTaskRun = false;
                        try {
                            mCheckConnectStateThread.join();
                        } catch (InterruptedException e) {
                            CASLog.e(TAG, "handle background timeout failed. " + e.getMessage());
                        }
                        mCheckConnectStateThread = null;
                    }
                    mProccessor.stopJniRecv();
                    mProccessor.stop(false);
                    mCurrentState = STATE_INIT;
                    mSurfaceView = null;
                    mActivity = null;
                    mViewGroup = null;
                    if (mStateListener != null) {
                        mStateListener.onNotify(CasState.CAS_BACKGROUND_TIMEOUT,
                                "Switch background timeout");
                    }
                }
            } catch (Exception e) {
                CASLog.e(TAG, "handleStateChangeCmd failed. " + e.getMessage());
            } finally {
                mCloudPhoneLock.notifyAll();
            }
        }
    }

    private void handleReconnectCmd() {
        if (!mIsStartSuccess) {
            mStateListener.onNotify(CasState.CAS_CONNECT_LOST, "Connect lost");
            return;
        }
        synchronized (mCloudPhoneLock) {
            toast = Toast.makeText(mContext, "Network connection disconnected,\nreconnecting.", Toast.LENGTH_LONG);
            try {
                if (mCurrentState == STATE_INIT || mCurrentState == STATE_PAUSE) {
                    return;
                }
                CASLog.i(TAG, "reconnect mAutoReconnectCount=" + mAutoReconnectCount);
                toast.show();
                mProccessor.stopJniRecv();
                long currentTs = System.currentTimeMillis();
                if (mProccessor.reconnect()) {
                    int status = mProccessor.getState();
                    if (status == JNIState.JNI_CONNECTED) {
                        mProccessor.startJniRecv();
                        mAutoReconnectCount = 0;
                        mAutoReconnectTimer = null;
                        mReconnecting = false;

                        if (System.currentTimeMillis() - currentTs < 1000) {
                            try {
                                // 重连时长不足1s等待1s，防止只出现重连成功的提示
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        toast.cancel();
                        Toast.makeText(mContext, "Reconnect success!", Toast.LENGTH_SHORT).show();
                        return;
                    }
                }
                CASLog.i(TAG, "reconnect failed");
                mAutoReconnectCount++;
                if (mAutoReconnectCount > RECONNECT_RETRY_MAX_COUNT) {
                    if (mStateListener != null) {
                        toast.cancel();
                        mStateListener.onNotify(CasState.CAS_CONNECT_LOST, "Connect lost");
                        mAutoReconnectCount = 0;
                        return;
                    }
                }

                if (mAutoReconnectTimer != null) {
                    mAutoReconnectTimer.cancel();
                }
                mAutoReconnectTimer = new Timer();
                mAutoReconnectTimer.schedule(new ReconnectTimerTask(), (long) RECONNECT_INTERVAL_TIME * 1000);
            } catch (Exception e) {
                CASLog.e(TAG, "handleReconnectCmd failed. " + e.getMessage());
            } finally {
                mCloudPhoneLock.notifyAll();
            }
        }
    }

    private boolean sendTouchEvent(int id, int action, final int x1, final int y1, final int pressure, long time) {
        int orientation = 0;
        int newOrientation = 0;
        if (mActivity != null) {
            try {
                orientation = mActivity.getRequestedOrientation();
            } catch (Exception e) {
                CASLog.e(TAG, "get orientation failed. " + e.getMessage());
                return true;
            }
        } else {
            return false;
        }

        switch (orientation) {
            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
                newOrientation = 0;
                break;
            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
                newOrientation = 1;
                break;
            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
                newOrientation = 2;
                break;
            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
                newOrientation = 3;
                break;
            default:
                CASLog.i(TAG, "invalid directation " + orientation);
                return false;
        }

        int x = x1, y = y1;
        if (newOrientation == 1 || newOrientation == 3) {
            x = x1 * mDisplayWidth / mDisplayHeight;
            y = y1 * mDisplayHeight / mDisplayWidth;
        }
        return mProccessor.sendTouchEvent(id, action, x, y, pressure, time, newOrientation, mDisplayHeight, mDisplayWidth);
    }

    private void initVirtualDeviceSession() {
        mVirtualDeviceSession = new VirtualDeviceSession(mContext);
        mRingBufferVirtualDeviceIO = new RingBufferVirtualDeviceIO();
        mVirtualDeviceSession.setVirtualDeviceIoHook(mRingBufferVirtualDeviceIO);
        if (mPermissionListener != null) {
            mVirtualDeviceSession.setPermissionListener(mPermissionListener);
        }
        mVirtualDevDataListener = new CloudPhoneVirtualDevDataListener() {
            @Override
            public void onRecvVirtualDevData(byte[] data, int length) {
                if (mRingBufferVirtualDeviceIO != null) {
                    mRingBufferVirtualDeviceIO.fillData(data);
                }
            }
        };
    }

    public static class JNIState {
        public static final int JNI_INIT = 0;
        public static final int JNI_LIB_INITIALIZED = 1;
        public static final int JNI_WORKER_THREADS_STARTED = 2;
        public static final int JNI_CONNECTED = 3;
        public static final int JNI_START = 4;
        public static final int JNI_START_FAILURE = 5;
        public static final int JNI_START_AUTH_FAILURE = 6;
        public static final int JNI_STOPPED = 9;
        public static final int JNI_CONNECTION_FAILURE = 10;
        public static final int JNI_INVALID = 15;
    }

    private class CmdHandler extends Handler {
        public CmdHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case CMD_START:
                    CASLog.i(TAG, "CMD_START");
                    handleStartCmd(mActivity, mViewGroup);
                    break;
                case CMD_SURFACE_CREATE:
                    CASLog.i(TAG, "CMD_SURFACE_CREATE");
                    handleSurfaceCreateCmd(msg);
                    break;
                case CMD_SURFACE_DESTROY:
                    CASLog.i(TAG, "CMD_SURFACE_DESTROY");
                    handleSurfaceDestroyCmd();
                    break;
                case CMD_SET_MEDIA_CONFIG:
                    CASLog.i(TAG, "CMD_SET_MEDIA_CONFIG");
                    handleSetMediaConfigCmd(msg);
                    break;
                case CMD_STATE_CHANGE:
                    CASLog.i(TAG, "CMD_STATE_CHANGE");
                    handleStateChangeCmd(msg);
                    break;
                case CMD_BACKGROUND_TIMEOUT:
                    CASLog.i(TAG, "CMD_BACKGROUND_TIMEOUT");
                    handleBackgroundTimeoutCmd();
                    break;
                case CMD_RECONNECT:
                    CASLog.i(TAG, "CMD_RECONNECT");
                    handleReconnectCmd();
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * listener
     * <p>receive jni notify msg</p>
     */
    public class CASListener {
        public void onRotationDirectionChange(int orientation) throws RemoteException {
            synchronized (this) {
                if (mOrientationChangeListener != null) {
                    mOrientationChangeListener.onOrientationChange(orientation);
                }
            }
        }

        public void onCmdRecv(int code, String describe) throws RemoteException {
            CASLog.i(TAG, "code = " + code + " msg = " + describe);
            Message msg = new Message();
            msg.what = CMD_STATE_CHANGE;
            msg.arg1 = code;
            mCmdHandler.sendMessage(msg);
            if (mStateListener != null) {
                mStateListener.onNotify(code, describe);
            }
        }

        public void onBitrateRecv(int bitrate) throws RemoteException {
            VirtualDeviceProtocol virtualDevice = mVirtualDeviceSession.getVirtualDevice();
            if (virtualDevice == null) {
                return;
            }
            VirtualDeviceManager virtualDeviceManager = virtualDevice.getVirtualDeviceManager(DEV_TYPE_CAMERA);
            if (virtualDeviceManager == null) {
                return;
            }

            int lastBitrate = ((VirtualCameraManager) virtualDeviceManager).getBitrate();
            // 根据mtrans回调码率，当码率变动超过一定阈值，动态更新上行数据传输码率
            if (Math.abs(bitrate - lastBitrate) >= 500000) {
                CASLog.i(TAG, "Recieve last Bitrate is " + lastBitrate + "， But recieve new Bitrate is " + bitrate);
                ((VirtualCameraManager) virtualDeviceManager).updateDynamicBitrate(bitrate);
            }
        }

        public void onChannelDataRecv(byte[] data) throws RemoteException {
            if (mChannelDataListener != null) {
                mChannelDataListener.onRecvCloudAppData(data);
            }
        }

        public void onVirtualDevDataRecv(byte[] data) throws RemoteException {
            if (mVirtualDevDataListener != null) {
                mVirtualDevDataListener.onRecvVirtualDevData(data, data.length);
            }
        }

        public void onImeMsgRecv(byte[] data) throws RemoteException {
            if (mImeMgr != null) {
                mImeMgr.processImeMsg(data);
            }
        }
    }

    private class BackgroundTimerTask extends TimerTask {
        @Override
        public void run() {
            CASLog.i(TAG, "BackgroundTimerTask run");
            synchronized (mCloudPhoneLock) {
                try {
                    if (mCmdHandler != null) {
                        Message msg = new Message();
                        msg.what = CMD_BACKGROUND_TIMEOUT;
                        CASLog.i(TAG, "CMD_BACKGROUND_TIMEOUT send");
                        mCmdHandler.sendMessage(msg);
                    }
                } catch (Exception e) {
                    CASLog.e(TAG, "BackgroundTimerTask run failed. " + e.getMessage());
                } finally {
                    mCloudPhoneLock.notifyAll();
                }
            }
        }
    }

    private class ReconnectTimerTask extends TimerTask {
        @Override
        public void run() {
            if (mCmdHandler != null) {
                Message msg = new Message();
                msg.what = CMD_RECONNECT;
                mCmdHandler.sendMessage(msg);
            }
        }
    }

    private class CheckConnectStateThread extends Thread {
        @Override
        public void run() {
            CASLog.i(TAG, "CheckConnectStateThread in");
            mReconnecting = false;
            while (mIsReconnectTaskRun) {
                if (!mReconnecting && (mCurrentState != STATE_PAUSE)) {
                    boolean isConnected = mProccessor.isConnect();
                    if (!isConnected) {
                        Message msg = new Message();
                        msg.what = CMD_RECONNECT;
                        mCmdHandler.sendMessage(msg);
                        mReconnecting = true;
                        mStateListener.onNotify(CAS_CONNECT_EXCEPTION, "Connection exception");
                    }
                }
                try {
                    if (!Thread.interrupted()) {
                        Thread.sleep(100);
                    }
                } catch (InterruptedException e) {
                    CASLog.e(TAG, "check connect state failed. " + e.getMessage());
                }
            }
            CASLog.i(TAG, "CheckConnectStateThread out");
        }
    }

    class TextWatchListener implements CloudPhoneTextWatchListener {
        @Override
        public void onTextChange(byte[] data) {
            int tryCount = 0;
            if (mCurrentState != STATE_START) {
                return;
            }
            while (!mProccessor.isConnect() || mProccessor.getState() != JNIState.JNI_CONNECTED) {
                if (tryCount >= SEND_IME_DATA_RETRY_MAX_COUNT) {
                    CASLog.e(TAG, "onTextChange retry failed. processor connect:" + mProccessor.isConnect() + ", processor state:" + mProccessor.getState());
                    return;
                }
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tryCount++;
            }
            mProccessor.sendData(JNIWrapper.IMEDATA, data);
        }
    }

}